/*
*    Phantom v2.0 GUI Automation Software
*    Copyright (C) 2009  John C. Van Arsdall (john@phantomtest.com)
*
*    This program is free software; you can redistribute it and/or modify
*    it under the terms of the GNU General Public License as published by
*    the Free Software Foundation; either version 2 of the License, or
*    (at your option) any later version.
*
*    This program is distributed in the hope that it will be useful,
*    but WITHOUT ANY WARRANTY; without even the implied warranty of
*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*    GNU General Public License for more details.
*
*    You should have received a copy of the GNU General Public License along
*    with this program; if not, write to the Free Software Foundation, Inc.,
*    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
*/
// Script.cpp: implementation of the CScript class.
//
//////////////////////////////////////////////////////////////////////

#include "ssi_script.h"

#include "ssi_file.h"
#include "ssi_singleinput.h"

//#define CRTDBG_MAP_ALLOC
//#define _CRTDBG_MAP_ALLOC   ** THIS JUNK PREVENTS BUILD SUDDENLY **
#include <stdlib.h>
#include <direct.h>
//#include <crtdbg.h>
#include "ssi_script.h"

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

CScript::CScript(ssi_stream* an_input, ssi_stream* an_output)
: debug_LastLine(0)
, debug_GoCtr(0)
, debug_ToLine(0)
{
	// Need this, else right now it will skip the first line
	bGotDebugFirstLine = 0;

	output = an_output;
	input = an_input;

	Engine = SSI_NULL;
  Interp = SSI_NULL;
  CurrLine = SSI_NULL;

  Interp = new CInterpreter((CInput*)this);
  Engine = new CEngine;
  Interp->SetEngine(Engine);
  Engine->SetInterpreter(Interp);

	Engine->SetOutDisplay(output);
  Interp->SetOutDisplay(output);

	Debugger = (CDebugger*)&ScriptDebugger;
	Debugger->input = input;
	Debugger->output = output;
}

CScript::~CScript()
{
  if(bDeleteMembers){
    zap(Engine);
    zap(Interp);
  }
	//_CrtDumpMemoryLeaks();  // Keeping in in debug mode slows things down!
}

SSI_BOOL CScript::Load(const String& FileName)
{
  //File aFile(FileName);

	String LocFile;
	input->getLocalFile(FileName, LocFile);
	File aFile(LocFile);
	//aFile.Name = FileName;
	//aFile.SetHandle(input->getFileHandle(FileName));


  if(!aFile.Status){
    if(output){
      String sErr;
      sErr.Format("Could not open script file: %s", FileName.Str());
      output->ErrorDisp(sErr.Str(), 1);
    }
    return 0;
  }

  //chdir(aFile.GetPath().Str());

  SetInputName(FileName);
	SetFileName(FileName);

  String Line;
  SSI_INT64 ctr = 0;
  CScriptLine sLine;
	ClearLines();
  while(aFile.ReadLine(Line) >= 0)
  {
    ctr++;
    sLine.Line = Line;

    sLine.LineNum = ctr;
    PushLine(sLine);
  }
  FilePath = FileName;
  return 1;
}

// Problem is occurring here... when running a
//  function, the only statements available to 
//  interpreter should be the statements of the
//  function... the interpreter's statement
//  set does not get replaced (save old one)
//  before here...  Perhaps clear the interpreter's
//  statement list?  And I think it is added here
//  via the run?

SSI_BOOL CScript::Run(SSI_INT64 Mode, CInterpreter* inInterp)
{
	//_CrtSetDbgFlag(_CRTDBG_CHECK_CRT_DF | _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);


	//_CrtSetBreakAlloc(8732);
	//_CrtSetBreakAlloc(3901066);
	//_CrtSetBreakAlloc(19072);
	START_PROFILE("CScript::Run")


	RunMode = Mode;

  if(inInterp == SSI_NULL){
    bDeleteMembers = 1;
  }
  else{
		if(Interp) delete Interp;
    if(Engine) delete Engine;
    Interp = inInterp;
    Engine = inInterp->GetEngine();
    bDeleteMembers = 0;
  }

  String OldPath;
  if(Interp->GetEngine()){
    CStatus Ret;
    String sFile = FilePath;
    OldPath = Interp->GetEngine()->Environment->GetCWD();
    if(!(Ret = Interp->GetEngine()->Environment->SetPathFromFile(sFile))){
      Interp->GetEngine()->GetOutStream()->Disp(Ret.GetStatusMessage().Str(), 1);
      return 0;
    }
  }


//	char* c = new char[1024];
//	_CrtMemCheckpoint( &s2 );
//	if ( _CrtMemDifference( &s2, &s1, &s3) ){
//		_CrtMemDumpStatistics( &s3 );
//	}

  CurrLine = SSI_NULL;
  CScriptLine aLine;
	ScriptNode* sNode = 0;
  CurrLine = GetTopLine();
  if(!CurrLine) return 0;

  //if(RunMode == NORMAL_MODE)
	if(RunMode == NORMAL_MODE || RunMode == DEBUG_MODE)
  {

    while(CurrLine)
    {
      // Need some method to handle flow control
      // Actually, this needs to be handled elsewhere,
      //   since flow control should be possible from 
      //   the command line.  This is also the case for
      //   handling functions
      
      CurrLine->Get(aLine);

      if(!Interp->Interpret(aLine.Line, CurrLine)) return 0;
			if(Interp->GetReturnState() || Interp->GetStopState()){
        break;
			}
      if(!CurrLine) return 0;
      CurrLine = CurrLine->next();
    }
  }

  if(Interp->GetEngine()){
    CStatus Ret;
    if(!(Ret = Interp->GetEngine()->Environment->SetCWD(OldPath))){
      Interp->GetEngine()->GetOutStream()->Disp(Ret.GetStatusMessage().Str(), 1);
      return 0;
    }
  }

	STOP_PROFILE("CScript::Run")
	REPORT_PROFILE("c:\\projects\\arithmescript\\profile.txt");

  return 1;
}


/*
SSI_BOOL CScript::GetDebug(String& Input){  

  SSI_CHAR Temp[MAX_SSI_CHARS];
  
	output->Disp("DEBUG >>");
	input->getline(Temp, MAX_SSI_CHARS); 
  
	Input = Temp;
	if(Input == "stop"){
		Interp->SetStopFlag();
		return 0;
	}
  return 1;
}
*/

SSI_BOOL CScript::GetNextLine(CScriptLine &Ret)
{
  if(!CurrLine) return 0;
  if(CurrLine->next()){
    CurrLine = CurrLine->next();
    CurrLine->Get(Ret);
    return 1;
  }
  return 0;
}

ScriptNode* CScript::DebugGetLine(CScriptLine& Ret)
{
	// Primarily used by the debugger
	ScriptNode* RetNode;
	if(!bGotDebugFirstLine){
		if(!CurrLine) return 0;
		CurrLine->Get(Ret);
		RetNode = CurrLine;
		// We do not want to advance the line because it will be
		//  handled the next time around.
		//CurrLine = CurrLine->next();
		bGotDebugFirstLine = 1;
	}else{
		if(GetNextLine(Ret)){
			RetNode = CurrLine;
		}else{
			RetNode = 0;
		}
	}
	return RetNode;
}

/*
SSI_INT64 CScript::ProcessDebug(){
	
	String Line;
	String Tok;
	String Delim = " ";
	String Rem;
	String sMess;
	SSI_INT64 Pos;

	int bLoop = 1;
	while(bLoop){
		GetDebug(Line);
		Line.Trim();
		if(Line == ""){
			// Allow <ENTER> to step through
			Line = "g 1";
		}
		if((Pos = Line.Parse(Delim, Rem, &Tok)) >= 0){
			if(Rem == "v"){
				CVariable aVar;
				aVar.Name = Line;
				aVar.SetType(dUNKNOWN);
				if(!Engine->GetVariable(aVar)){
					sMess.Format("Variable %s not found", aVar.Name.Str());
					output->Disp(sMess, 1);
				}else{
					sMess.Format("%s", aVar.GetWorkingData().GetString().Str());
					output->Disp(sMess, 1);
				}
			}else if(Rem == "e"){
				// OK, so 'e' doesnt work because it doesnt add a statement to the statement list
				Interp->Interpret(Line, SSI_NULL);  // In debug mode, so dont send a curr node
				if(Interp->GetReturnState() || Interp->GetStopState()){
					return 0;
				}
			}else if((Rem == "g") || (Rem == "t")){
				int linenumber;
				if(sscanf(Line.Str(), "%d", &linenumber) != 1){
					sMess.Format("DEBUG ERROR: 'g' and 't' format is 'g[ number]'");
					output->Disp(sMess, 1);
				}else{
					if(Rem == "t"){
						debug_ToLine = linenumber;
					}else{
						debug_GoCtr = linenumber;
					}
					bLoop = 0;
				}
			}else if(Rem == "+b"){
				int LineNum;
				char buff[1024];
				if(sscanf(Line.Str(), "%d %s", &LineNum, buff) != 2){
				}else{
					CBreakpoint aBP;
					aBP.LineNumber = LineNum;
					aBP.FileName = buff;
					Interp->AddBreakpoint(aBP);
				}
			}else if(Rem == "-b"){
				int LineNum;
				char buff[1024];
				if(sscanf(Line.Str(), "%d %s", &LineNum, buff) != 2){
				}else{
					CBreakpoint aBP;
					aBP.LineNumber = LineNum;
					aBP.FileName = buff;
					Interp->RemoveBreakpoint(aBP);
				}
			}else{
				bLoop = 0;
			}
		}else{
			bLoop = 0;
		}
	}
	return 1;
}
*/

SSI_INT64 CScript::Query(const CScriptStatement& aStatement)
{
	if(RunMode == NORMAL_MODE){
		return 1;
	}

	if(Debugger){
		//if(!Debugger->Process(Engine, Interp, (CInput*)this, aStatement)){
  		if(!Debugger->Process(Interp->GetEngine(), Interp, (CInput*)this, aStatement)){
  		
			return 0;
		}
	}
	return 1;
}

SSI_INT64 CScript::CScriptDebugger::Process(CEngine* Engine, CInterpreter* Interp, CInput* Input,  const CScriptStatement& aStatement){

	//static int doVERBOSE = 0;

	if(!Engine){
		return 0;
	}

	if(!Interp){
		return 0;
	}

	if(!Input){
		return 0;
	}

	if(!output){
		return 0;
	}

	if(!input){
		return 0;
	}

	// Iterators for input list and stop state
	vector<CInput*>::iterator itor;
	vector<int>::iterator stor;

	///////////// OK, I think this is worthy of some better commenting... ////////
	// InputList is a vector of Input classes.  Each Input represents a part of the
	// call stack (that is, each input is a function or file, the hierarchy to the
	// current line.

	// If this is the first input (i.e. top level), Initialize th input list
	if(InputList->size() <= 0){
		InputList->push_back(Input);
		StopStates->push_back(1);
		debug_LastLine = 0;
	}


	int newInput = 0;  // This is used in conjunction with JumpState...

	// If Input not found in list, add it
	itor = find(InputList->begin(), InputList->end(), Input);
	if(itor == InputList->end()){
		InputList->push_back(Input);
		StopStates->push_back(0);
		itor = find(InputList->begin(), InputList->end(), Input);

		// LastLine no longer valid, force stop state check
		debug_LastLine = 0;
		newInput = 1;
	}

	itor++;
	int sz = InputList->size();

	// If not the last input, clear all remaining ones
	if(itor != InputList->end()){
		InputList->erase(itor, InputList->end());
		sz = sz - InputList->size();

		// Back up in stop state list
		int ctr = 1;
		stor = StopStates->end();
		--stor;
		while(ctr < sz){
			ctr++;
			--stor;
		}

		StopStates->erase(stor, StopStates->end());

		sz = StopStates->size();  // Just for checking... remove later
		if(sz != InputList->size()){
			sz = InputList->size();
      Engine->GetOutStream()->ErrorDisp("INTERNAL ERROR IN CSCRIPTDEBUGGER::PROCESS", 1);
		}

		// If breaking out of Input, LastLine not valid and force JumpState check
		debug_LastLine = 0;
		newInput = 1;
	}		

	// Get last in input list and stop state
	itor = InputList->end();  // End is some ghost position
	--itor;
	stor = StopStates->end();
	--stor;

	// If it was a new input, and the JumpState is set, step into
	if(JumpState && newInput){
		*stor = 1;
		JumpState = 0;
	}

	// Dont show same line twice
	if(debug_LastLine == aStatement.LineID){
		return 1;
	}
	debug_LastLine = aStatement.LineID;

	// Get the current line
	ScriptNode* aNode = Input->GetByID(aStatement.LineID);
	if(!aNode){
		return 0;
	}
	CScriptLine aLine;
	aNode->Get(aLine);

	// Print line if verbose mode is on
	String Line;
	//doVERBOSE = 1;
	if(doVERBOSE){
		Line.Format("[%s] %d: %s", Input->GetFileName().Str(), aLine.LineNum, aLine.Line.Str());
		output->Disp(Line.Str(), 1);
	}

	// Breakpoint check
	CBreakpoint aBP;
	aBP.LineNumber = aLine.LineNum;
	aBP.FileName = Input->GetFileName();
	if(CheckBreakpoint(aBP) > 0){
    if(doVERBOSE){
		  Line.Format("  {{ BP Hit - Line %d (%s) }}  ", aLine.LineNum, aBP.FileName.Str()); 
		  output->Disp(Line.Str(), 1);
    }
		SetGoCounter(0);
		SetToLine(0);

		// Set stops for all previous inputs
		vector<int>::iterator stor_temp;
		stor_temp = stor;
		while(*stor_temp != 1){
			*stor_temp = 1;
			if(stor_temp == StopStates->begin()){
				break;
			}
			--stor_temp;
		}
	}

	// If not a stop state, return
	if(*stor == 0){
		return 1;
	}

	// Check Go Counter
	if(debug_GoCtr > 0){
		SetGoCounter(debug_GoCtr - 1);
		return 1;
	}else if(debug_GoCtr < 0){
		return 1;  // OK, so this will go until end of script or breakpoint
	}

	// Check To Line
	if(debug_ToLine > 0){
		if(debug_ToLine <= aLine.LineNum){
			SetToLine(0);
		}else{
			return 1;
		}
	}

	// Debug command processing --------------------------------------------
	String Tok;
	String Delim = " ";
	String Rem;
	SSI_INT64 Pos;
	String sMess;

	// Handle User Input
	int bLoop = 1;
	while(bLoop){
		if(GetDebugInput(Input->GetFileName(), aLine, Line)){
		}
		Line.Trim();
		
		if(Line == ""){
			// Allow <ENTER> to step through
			Line = "g 1";
		}
		if((Pos = Line.Parse(Delim, Rem, &Tok)) >= 0){
			if(Rem == "v"){
				DisplayVariable(Line, Engine);
			}else if(Rem == "e"){
				// OK, so 'e' doesnt work because it doesnt add a statement to the statement list

        //CScriptStatementList aList;
        //Interp->SeparateToStatements(Line, aList);
        //Interp->Interpret(Line, aList.GetCurrentStatement());

        CSingleInput Single;
        Single.SetInput(Line, "DEBUG Evaluate");
        Single.Run(Interp->GetEngine());


        //Interp->Interpret(Line, SSI_NULL);  // In debug mode, so dont send a curr node
				if(Interp->GetReturnState() || Interp->GetStopState()){
					return 0;
				}
			}else if((Rem == "g") || (Rem == "t")){
				int linenumber;
				if(sscanf(Line.Str(), "%d", &linenumber) != 1){
					sMess.Format("'g' and 't' format is 'g[ number]'");
					DisplayDebugError(sMess);
				}else{
					if(Rem == "t"){
						SetToLine(linenumber);
					}else{
						SetGoCounter(linenumber);
						if(debug_GoCtr > 0){
							SetGoCounter(debug_GoCtr - 1);
						}
					}
					bLoop = 0;
				}
			}else if(Rem == "+b"){
				int LineNum;
				char buff[1024];
				if(sscanf(Line.Str(), "%d %[^\0]s", &LineNum, buff) != 2){
				}else{
					CBreakpoint aBP;
					aBP.LineNumber = LineNum;
					aBP.FileName = buff;
					AddBreakpoint(aBP);
				}
			}else if(Rem == "-b"){
				int LineNum;
				char buff[1024];
				if(sscanf(Line.Str(), "%d %[^\0]s", &LineNum, buff) != 2){
				}else{
					CBreakpoint aBP;
					aBP.LineNumber = LineNum;
					aBP.FileName = buff;
					RemoveBreakpoint(aBP);
				}
			}else{
				bLoop = 0;
			}
		}else{
			// Jump in function
			if(Line == "j"){
				JumpState = 1;
				SetToLine(0);
				SetGoCounter(0);
				bLoop = 0;
			// Jump out of function
			}else if(Line == "o"){
				*stor = 0;
				bLoop = 0;
			}else if(Line == "q"){
        Interp->SetStopFlag();
			}else if(Line == "verbose"){
        if(doVERBOSE){
          doVERBOSE = 0;
          output->Disp("verbose OFF", 1);
        }else{
          doVERBOSE = 1;
          output->Disp("verbose ON", 1);
        }
      }else if((Line == "?") || (Line == "help")){
        output->Disp("++++ Debug Commands ++++", 1);
        output->Disp("<ENTER>    - Steps one line", 1);
        output->Disp("v {var}    - Displays the value of variable {var}", 1);
        output->Disp("g {#}      - Steps {#} lines", 1);
        output->Disp("t {#}      - Runs to {#} line number", 1);
        output->Disp("+b {#} {f} - Sets a breakpoint at line {#} in file {f}", 1);
        output->Disp("-b {#} {f} - Removes a breakpoint at line {#} in file {f}", 1);
        output->Disp("e {s}      - Evaluates statement {s}", 1);
        output->Disp("j          - Jumps in to the current function", 1);
        output->Disp("o          - Jumps out of the current function", 1);
        output->Disp("q or stop  - Quits the script", 1);
        output->Disp("verbose    - Toggles the verbose mode on and off", 1);
        output->Disp("? or help  - Displays this help message", 1);

      }else if(Line == "stop"){
				return 0;
			}else{
				bLoop = 0;
			}
		}
	}
	return 1;
}

CScript::CScriptDebugger::CScriptDebugger() : 
JumpState(0),
StopStates(0),
doVERBOSE(0)
{
	InputList = new vector<CInput*>;
	StopStates = new vector<int>;
}

CScript::CScriptDebugger::~CScriptDebugger(){
	if(InputList){
		delete InputList;
		InputList = 0;
	}
	if(StopStates){
		delete StopStates;
		StopStates = 0;
	}
}

SSI_INT64 CScript::CScriptDebugger::GetDebugInput(String& FileName, CScriptLine& CurrLine, String& Input)
{

	SSI_CHAR Buff[4096];
	SSI_INT64 cnt = input->getDebug(CurrLine.LineNum, FileName.Str(), Buff, 4096);
	if(cnt >= 4096){
		String sErr = "Maximum input is 4095 characters";
		DisplayDebugError(sErr);
		return 0;
	}
	if(cnt < 0){
		return 0;
	}
	Buff[4095] = 0;
	Buff[cnt] = 0;
	Input = Buff;
	return 1;

  /*
	SSI_CHAR Buff[4096];
	output->Disp("DEBUG >>");
	SSI_INT64 cnt = input->getline(Buff, 4096);
	if(cnt >= 4096){
		String sErr = "Maximum input is 4095 characters";
		DisplayDebugError(sErr);
		return 0;
	}
	Buff[4095] = 0;
	Input = Buff;
	return 1;
  */
}

/*
void CScript::SetLog(String& FileName)
{
  if(Engine){
    Engine->StartLog(FileName);
  }
}
*/

int CScript::EvaluateArguments(const String& Args)
{
  if(this->Interp){
    CSingleInput Single;
    Single.SetInput(Args, "Arguments");
    return Single.Run(Engine);
  }
  return 0;
}



